//TrigonometricFunc module
//Processing time: max. 15T

module TrigonometricFunc(
	iRST_N,
	iCLK,
	iStartProcess,
	iFunc,
	oBusy,
	oError,
	iData,
	oData
);

localparam PI			= 31'h40490fdb;	// Pi
localparam _PI			= 32'hc0490fdb;	// -Pi
localparam PI2			= 31'h40c90fdb;	// 2*Pi
localparam PI_2		= 31'h3FC90FDB;	// Pi/2
localparam PI3_2		= 31'h4096CBE4;	// 3*Pi/2
localparam C8192_PI	= 31'h4522F983;	// 8192 / Pi
localparam C4096		= 31'h45800000;	// 4096
localparam _1			= 32'hbf800000;	// -1
localparam C1			= 31'h3f800000;	// +1
localparam _C1			= 32'hbf800000;	// +1

input [31:0] iData;
output reg [31:0] oData;

input iRST_N,iStartProcess,iCLK;
output oBusy;
assign oBusy = process_started;
input [1:0] iFunc; //00: Sin; 01: Cos; 10: Acos; 11: Error
output reg oError;

reg prev_iStartProcess;
reg process_started;
reg [31:0] data_in;
reg [4:0] state;
reg [5:0] timer;
reg neg;
reg[13:0] pre_addr;
reg[1:0] func;

always@(posedge iCLK or negedge iRST_N)
begin
	if(!iRST_N)	begin
		process_started <= 1'b0;
		prev_iStartProcess <= 1'b1;
		state <= 5'd0;
		timer <= 6'd0;
		oError <= 1'b0;
		oData <= 0;
	end
	else begin
		prev_iStartProcess <= iStartProcess;		
		if({prev_iStartProcess,iStartProcess} == 2'b01 && !process_started) begin
			func <= iFunc;
			data_in <= iData;			
			timer <= 6'd0;
			process_started <= 1'b1;
			oError <= 0;
			
			if(iFunc[1] == 1'b0) begin
				if(iFunc[0] == 1'b0) neg <= iData[31];
				else neg <= 0;				
				Mult1A <= {1'b0, iData[30:0]};
				Mult1B <= C8192_PI;
				state <= 5'd1;
			end
			else if(iFunc[0] == 1'b0) begin
				if(iData[30:0] > C1) state <= 5'd0;	// Error
				else begin
					Mult1A <= {1'b0, iData[30:0]};
					Mult1B <= C4096;
					state <= 5'd5;
				end
			end
			else state <= 5'd0;	// Error			
		end
		
		else if(process_started) begin
			case (state)
				//--------------------------------------------
				//Error state
				5'd0: begin
					process_started <= 1'b0;
					oError <= 1'b1;
					oData <= 32'h7FFFFFFF;
				end
				//--------------------------------------------
				//Sin and Cos state				
				5'd1: begin				
					timer <= timer + 1'b1;					
					if(timer == 6'd4) begin			//wait till mult finished
						timer <= 6'd0;
						state <= 5'd2;
						FloatToIntA <= Mult1Res;
					end
				end
				5'd2: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd5) begin			//wait till convert finished
						timer <= 6'd0;
						state <= 5'd3;
						pre_addr <= FloatToIntRes + (func[0] == 1'b1 ? 4096 : 0);						
					end
				end
				5'd3: begin
					if(pre_addr < 4096) SinTableROM_address <= pre_addr[11:0];
					else if(pre_addr == 4096) begin
						oData <= C1;
						process_started <= 1'b0;
					end
					else if(pre_addr < 8192) SinTableROM_address <= 8192 - pre_addr[12:0];
					else begin
						neg <= ~neg;
						if(pre_addr < 12288) SinTableROM_address <= pre_addr - 8192;
						else if(pre_addr == 12288) begin
							oData <= _C1;
							process_started <= 1'b0;
						end
						else SinTableROM_address <= 16384 - pre_addr;
					end
					state <= 5'd4;
				end
				5'd4: begin					
					timer <= timer + 1'b1;					
					if(timer == 6'd1) begin		//wait till read finished
						timer <= 6'd0;	
						oData[31] <= neg;
						oData[30:0] <= SinTableROM_q[30:0];	//set the output data							
						process_started <= 1'b0;
					end
				end
				
				//--------------------------------------------
				//ACos calculation
				//data_in is in the interval of -1->1						
				5'd5: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd4) begin			//wait till mult finished
						timer <= 6'd0;
						state <= 5'd6;
						FloatToIntA <= Mult1Res;
					end
				end
				5'd6: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd5) begin			//wait till conversion finished
						timer <= 6'd0;
						state <= 5'd7;
						if(FloatToIntRes == 4096) begin
							if(data_in[31] == 1'b0) oData <= 0;
							else oData <= PI;
							process_started <= 1'b0;
						end
						else AcosTableROM_address <= FloatToIntRes[11:0];
					end
				end				
				5'd7: begin					
					timer <= timer + 1'b1;					
					if(timer == 6'd1) begin			//wait till read finished
						timer <= 6'd0;	
						if(data_in[31] == 1'b0) begin
							oData <= AcosTableROM_q;
							process_started <= 1'b0;
						end
						else begin
							Add1A <= PI;
							Add1B <= {1'b1, AcosTableROM_q[30:0]};
							state <= 5'd8;
						end
					end
				end
				5'd8: begin
					timer <= timer + 1'b1;					
					if(timer == 6'd7) begin			//wait till add finished
						oData <= Add1Res;
						process_started <= 1'b0;
					end
				end				
			endcase
		end				
	end
end

//7T
reg [31:0] Add1A;
reg [31:0] Add1B;
wire [31:0] Add1Res;
SinglePrecFloatAdd u1(
	.clock(~iCLK),
	.dataa(Add1A),
	.datab(Add1B),
	.result(Add1Res)
);

//6T
reg [31:0] FloatToIntA;
wire [31:0] FloatToIntRes;
SinglePrecFloatToInt u5(
	.clock(~iCLK),
	.dataa(FloatToIntA),
	.result(FloatToIntRes)
);

reg [11:0] SinTableROM_address;
wire [31:0] SinTableROM_q;
SinTableROM u6(
	.address(SinTableROM_address),
	.clock(~iCLK),
	.q(SinTableROM_q)
);

reg [11:0] AcosTableROM_address;
wire [31:0] AcosTableROM_q;
AcosTableROM u8(
	.address(AcosTableROM_address),
	.clock(~iCLK),
	.q(AcosTableROM_q)
);

//5T
reg[31:0] Mult1A, Mult1B;
wire[31:0] Mult1Res;
SinglePrecFloatMult m1(
	.dataa(Mult1A),
	.datab(Mult1B),
	.result(Mult1Res),
	.clock(~iCLK)
);

endmodule
